package de.unibi.comet.fa;

import java.util.List;

import de.unibi.comet.util.BitArray;

/** Represents a string, where each position consists of a set of characters. */
public class GeneralizedString {
	private BitArray[] positions;
	private Alphabet alphabet;

	/** Constructs the generalized string from a simple string allowed to contain
	 *  wildcards ("?").*/
	public GeneralizedString(Alphabet alphabet, String s) {
		this.alphabet=alphabet;
		positions = new BitArray[s.length()];
		for (int i=0; i<s.length(); ++i) {
			positions[i]=new BitArray(alphabet.size());
			if (s.charAt(i)=='?') {
				positions[i].invert();
			} else {
				int index = alphabet.getIndex(s.charAt(i));
				if (index<0) throw new IllegalArgumentException(String.format("Unknown character \"%s\" encountered", s.charAt(i)));
				positions[i].set(index, true);				
			}
		}
	}
	
	/** General constructor. Expects for each position the set of expected
	 *  characters in terms of a BitArray (w.r.t. the alphabet). */
	public GeneralizedString(Alphabet alphabet, BitArray[] positions) {
		this.positions=positions;
		this.alphabet=alphabet;
	}

	/** General constructor. Expects for each position the set of expected
	 *  characters in terms of a BitArray (w.r.t. the alphabet). */
	public GeneralizedString(Alphabet alphabet, List<BitArray> positions) {
		this.positions=new BitArray[positions.size()];
		int i=0;
		for (BitArray ba : positions) this.positions[i++]=ba;
		this.alphabet=alphabet;
	}

	/** Constructor for a single letter string. */
	public GeneralizedString(Alphabet alphabet, BitArray charSet) {
		this.alphabet=alphabet;
		this.positions=new BitArray[1];
		this.positions[0]=charSet;
	}
	
	/** Concatenates this string with another (over the same alphabet). */
	public void concat(GeneralizedString gs) {
		BitArray[] newPositions = new BitArray[this.positions.length+gs.positions.length];
		System.arraycopy(this.positions, 0, newPositions, 0, this.positions.length);
		System.arraycopy(gs.positions, 0, newPositions, this.positions.length, gs.positions.length);
		positions=newPositions;
	}

	/** Concatenates a list of BitArrays (over the same alphabet) to this. */
	public void concat(List<BitArray> l) {
		BitArray[] newPositions = new BitArray[this.positions.length+l.size()];
		System.arraycopy(this.positions, 0, newPositions, 0, this.positions.length);
		int i = this.positions.length;
		for (BitArray ba : l) {
			newPositions[i++]=ba;
		}
		positions=newPositions;
	}
	
	public BitArray getPosition(int index) { return positions[index]; }
	
	public int length() { return positions.length; }
	
	/** Returns true if the this generalized string matches the given string 
	 *  starting from given position. */
	public boolean matches(String s, int position) {
		boolean mapUnknown = alphabet.contains('#');
		for (int i=0; i<positions.length; ++i) {
			if (i+position>=s.length()) return false;
			char c = s.charAt(i+position);
			// map all other (those not in the pattern) characters onto #
			int index = alphabet.getIndex(c);
			if (index<0) {
				if (mapUnknown) c='#';
				else throw new IllegalArgumentException(String.format("Unknown character \"%s\" encountered", c));
			}
			if (!positions[i].get(index)) return false;
		}
		return true;
	}
	
	public String toString() {
		StringBuffer sb = new StringBuffer();
		for (BitArray ba : positions) {
			sb.append("(");
			int i=-1;
			for (boolean b : ba) {
				++i;
				if (!b) continue;
				sb.append(alphabet.get(i));
			}
			sb.append(")");
		}
		return sb.toString();
	}

	public Alphabet getAlphabet() {
		return alphabet;
	}
	
	/** Returns the probability that this generalized string occurs. */
	public double getProbability(double[] characterDistribution) {
		double result = 1.0;
		for (BitArray ba : positions) {
			double p = 0.0;
			for (int i=0; i<ba.size(); ++i) {
				if (ba.get(i)) p+=characterDistribution[i];
			}
			result*=p;
		}
		return result;
	}
	
}
